package ru.niir.goodfil.utils;

import org.apache.commons.lang.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class Utils {
    // НАСТРОЙКИ РАБОТЫ С ASSERTАМИ -------------------------------------------------------------------------------

    /**
     * Определяет, будет ли в stdout выводиться сообщение в случае срабатывания assert'a
     */
    private static boolean assertUseStdout = true;

    /**
     * Определяет, будет ли вызываться System.exit(1) в случае срабатывания assert'a
     */
    private static boolean assertUseSystemExit = false;

    /**
     * Определяет, будет ли выбрасываться исключение AssertionError в случае срабатывания assert'a
     */
    private static boolean assertUseException = true;

    static {
        System.out.println("Assertion options: ");
        System.out.println("STDOUT    : " + assertUseStdout);
        System.out.println("EXIT      : " + assertUseSystemExit);
        System.out.println("EXCEPTION : " + assertUseException);
    }

    /**
     * Возвращает true, если в случае срабатывания assertа будет выводиться сообщение в stdout
     */
    public static boolean isAssertUseStdout() {
        return assertUseStdout;
    }

    /**
     * Если установить значение true, то в случае срабатывания asserta будет выводиться сообщение в stdout
     */
    public static void setAssertUseStdout(boolean assertUseStdout) {
        Utils.assertUseStdout = assertUseStdout;
    }

    /**
     * Возвращает true, если в случае срабатывания assertа приложение будет завершаться через вызов System.exit(1)
     */
    public static boolean isAssertUseSystemExit() {
        return assertUseSystemExit;
    }

    /**
     * Если установить значение true, то в случае срабатывания asserta приложение будет завершаться через вызов
     * System.exit(1)
     */
    public static void setAssertUseSystemExit(boolean assertUseSystemExit) {
        Utils.assertUseSystemExit = assertUseSystemExit;
    }

    /**
     * Возвращает true, если в случае срабатывания assertа будет генерироваться исключение AssertionError
     */
    public static boolean isAssertUseException() {
        return assertUseException;
    }

    /**
     * Если установить значение true, то в случае срабатывания asserta будет генерироваться исключение AssertionError
     */
    public static void setAssertUseException(boolean assertUseException) {
        Utils.assertUseException = assertUseException;
    }

    // ------------------------------------------------------------------------------------------------------------

    public static String s(String s) {
        return s == null ? "" : s.trim();
    }

    //  ASSERTS ------------------------------------------------------------------------------------------------------------------

    /**
     * Ручная реализация assertов. Потому что так проще. Основная функция assert. Все остальные вызывают эту.
     *
     * @param v       проверяемое значение
     * @param message сообщение об ошибке
     */
    public static void _assert(boolean v, String message) {
        if (!v) {
            if (assertUseStdout) {
                System.out.println("!!! ASSERTION ERROR !!!");
                if (!StringUtils.isBlank(message)) System.out.println("message  : " + message);
                System.out.println("location : ");
                StackTraceElement[] els = Thread.currentThread().getStackTrace();
                for (int i = 0; i < els.length; i++) {
                    System.out.println(els[i]);
                }
            }

            if (assertUseSystemExit) {
                System.exit(1);
            }

            if (assertUseException) {
                if (!StringUtils.isBlank(message)) {
                    throw new AssertionError(message);
                } else {
                    throw new AssertionError();
                }
            }
        }
    }

    public static void _assert(boolean v) {
        _assert(v, null);
    }

    public static void _assertNotNull(Object o) {
        if (o == null) _assert(false, null);
    }

    public static void _assertNotNull(Object o, String message) {
        if (o == null) _assert(false, message);
    }

    public static void _assertNotBlank(String s) {
        if (StringUtils.isBlank(s)) _assert(false);
    }

    public static void _assertNotBlank(String s, String message) {
        if (StringUtils.isBlank(s)) _assert(false, message);
    }

    //  --------------------------------------------------------------------------------------------------------------------------

    //  Работа с датами ----------------------------------------------------------------------------------------------------------

    public static void main(String[] args) {
        __test_getEndOfCurrentMonth();
    }

    public static void __test_getBeginOfPreviousMonth() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        Calendar currentDate = Calendar.getInstance();
        Calendar beginOfPreviousMonth = getBeginOfPreviousMonth();
    }

    public static void __test_getBeginOfCurrentMonth() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        Calendar currentDate = Calendar.getInstance();
        Calendar beginOfCurrentMonth = getBeginOfCurrentMonth();
    }

    public static void __test_getEndOfPreviousMonth() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        Calendar currentDate = Calendar.getInstance();
        Calendar endOfPreviousMonth = getEndOfPreviousMonth();
    }

    public static void __test_getEndOfCurrentMonth() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        Calendar currentDate = Calendar.getInstance();
        Calendar endOfCurrentMonth = getEndOfCurrentMonth();
    }

    /**
     * Получить разницу в днях менжду двумя датами. Функция инкапсулирует описанный функционал по причине того, что,
     * оказывается, разницу между двумя датами можно посчитать несколькими способами. (В качестве простого примера:
     * можно учитывать при подсчете летнее/зимнее время (+- час в сутки), а можно не учитывать.)
     * <p/>
     * Описание этого вопроса например тут: http://www.xmission.com/~goodhill/dates/deltaDates.html.
     * <p/>
     * На текущий момент используется реализация FAQCalendar, взятая с приведенног выше сайта.
     *
     * @param beg дата начала (в миллисекундах)
     * @param end дата окончания (в миллисекундах)
     * @return разница между двумя датами в днях
     * @see ru.niir.goodfil.utils.FAQCalendar реализация алгоритма определения разницы между двумя датами в днях
     */
    public static long differenceInDays(long beg, long end) {
        FAQCalendar begDate = new FAQCalendar(beg);
        FAQCalendar endDate = new FAQCalendar(end);

        _assert(begDate.before(endDate), "Дата создания клиента должна быть раньше текущей даты");

        return begDate.diffDayPeriods(endDate);
    }

    /**
     * Получить дату начала предыдущего меясяца. Если сегодня 08.02.2010, то результатом работы функции будет 01.01.2010
     * 00:00.
     */
    public static Calendar getBeginOfPreviousMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar;
    }

    /**
     * Получить дату начала текущего меясяца. Если сегодня 08.02.2010, то результатом работы функции будет 01.02.2010
     * 00:00.
     */
    public static Calendar getBeginOfCurrentMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar;
    }

    /**
     * Получить дату окончания предыдущего меясяца. Если сегодня 08.02.2010, то результатом работы функции будет
     * 31.01.2010 00:00 (НЕ 23:59). СТОИТ ОБРАТИТЬ ВНИМАНИЕ НА ТО, ЧТО 24часа последнего дня месяца НЕ УЧИТЫВАЮТСЯ, т.е.
     * в приведенном выше примере возвращается 31.01.2010 00:00 а не 31.01.2010 23:59.
     */
    public static Calendar getEndOfPreviousMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.add(Calendar.DATE, -1);
        return calendar;
    }

    /**
     * Получить дату окончания текущего меясяца. Если сегодня 08.02.2010, то результатом работы функции будет 28.02.2010
     * 00:00 (НЕ 23:59). СТОИТ ОБРАТИТЬ ВНИМАНИЕ НА ТО, ЧТО 24часа последнего дня месяца НЕ УЧИТЫВАЮТСЯ, т.е. в
     * приведенном выше примере возвращается 28.02.2010 00:00 а не 31.01.2010 23:59.
     */
    public static Calendar getEndOfCurrentMonth() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 0);
        return calendar;
    }

    //  --------------------------------------------------------------------------------------------------------------------------

    private static final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");

    public static long date(String s) throws ParseException {
        return sdf.parse(s).getTime();
    }

    public static boolean stringsEquals(String s1, String s2) {
        if (s1 == null && s2 == null) {
            return true;
        } else if (s1 == null || s2 == null) {
            return false;
        } else {
            return s1.equals(s2);
        }
    }
}

